package _back_tracking

import org.junit.Assert
import org.junit.Test

/*
https://programmercarl.com/0040.%E7%BB%84%E5%90%88%E6%80%BB%E5%92%8CII.html
https://leetcode.cn/problems/combination-sum-ii/description/

40. 组合总和 II
给定一个候选人编号的集合 candidates 和一个目标数 target ，找出 candidates 中所有可以使数字和为 target 的组合。
candidates 中的每个数字在每个组合中只能使用 一次 。
注意：解集不能包含重复的组合。

示例 1:
输入: candidates = [10,1,2,7,6,1,5], target = 8,
输出:
[
[1,1,6],
[1,2,5],
[1,7],
[2,6]
]

示例 2:
输入: candidates = [2,5,2,1,2], target = 5,
输出:
[
[1,2,2],
[5]
]
 */
class leetcode_40 {
    @Test
    fun test_1() {
        val actual = combinationSum2(candidates = intArrayOf(10, 1, 2, 7, 6, 1, 5), 8)
        val expect: ArrayList<ArrayList<Int>> = arrayListOf(arrayListOf(1, 1, 6), arrayListOf(1, 2, 5), arrayListOf(1, 7), arrayListOf(2, 6))
        Assert.assertEquals(expect.toString(), actual.toString())
    }

    @Test
    fun test_2() {
        val actual = combinationSum2(candidates = intArrayOf(2, 5, 2, 1, 2), 5)
        val expect: ArrayList<ArrayList<Int>> = arrayListOf(arrayListOf(1, 2, 2), arrayListOf(5))
        Assert.assertEquals(expect.toString(), actual.toString())
    }


    private fun combinationSum2(candidates: IntArray, target: Int): List<List<Int>> {
        // 回溯法
        /*
        注意：
        1 一个组合中可以使用下标不同旦数值相同的元素。 => 对应树枝
        同一树层上两个重复元素不可以重复选取。2个组合之间不能重复 => 对应树层
        对同一个树层去重，要需要先排序。
        2 去重：
        树枝去重
        树层去重 - 此题
         */
        candidates.sort()
//        println("sorted =${candidates.contentToString()}")
        val path: ArrayList<Int> = ArrayList<Int>()
        val result: ArrayList<ArrayList<Int>> = ArrayList<ArrayList<Int>>()
        // TODO: new BooleanArray
        val used: BooleanArray = BooleanArray(candidates.size)
        backtracking(candidates, target, 0, 0, path, result, used)

        return result
    }

    private fun backtracking(candidates: IntArray, target: Int, startIndex: Int, sum: Int, path: ArrayList<Int>, result: ArrayList<ArrayList<Int>>, used: BooleanArray) {
        // 终止条件
        // 收集结果
        // println("path=${path.toString()}")
        if (sum == target) {
            val items: ArrayList<Int> = ArrayList<Int>()
            items.addAll(path)
            result.add(items)
            return
        }

        for (i in startIndex..candidates.size - 1) { // [startIndex, end index]
            // 处理
            if (sum + candidates[i] > target) { // 剪枝
                return
            }
            // TODO:去重复条件
            if (i > 0 && candidates[i] == candidates[i - 1] && used[i - 1] == false) { // 树层去重
                continue
            }
            // println("used: ${used.contentToString()}")
            used[i] = true
            path.add(candidates[i])

            // 递归
            // 同一个元素不能重复：i+1
            backtracking(candidates, target, i + 1, sum + candidates[i], path, result, used)

            // 回溯
            used[i] = false
            path.remove(candidates[i])
        }
    }
}